import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Objects;
import java.util.Random;

/**
 * 常用类
 * 介绍Java提供的一些常用类，如String、Math、BigDecimal等的用法。
 * <p>
 * Object类是所有类、数组、枚举类的父类，
 * 任何Java对象都可以调用Object类的方法。
 * Object类提供了如下几个常用方法。
 * ➢ boolean equals(Object obj)：判断指定对象与该对象是否相等。此处相等的标准是，两个对象是同一个对象，因此该equals()方法通常没有太大的实用价值。
 * ➢ protected void finalize()：当系统中没有引用变量引用到该对象时，垃圾回收器调用此方法来清理该对象的资源。
 * ➢ Class<?> getClass()：返回该对象的运行时类，
 * ➢ int hashCode()：返回该对象的hashCode值。在默认情况下，Object类的hashCode()方法根据该对象的地址来计算
 * （即与System.identityHashCode(Object x)方法的计算结果相同）。
 * 但很多类都重写了Object类的hashCode()方法，不再根据地址来计算其hashCode()方法值。
 * ➢ String toString()：返回该对象的字符串表示，当程序使用System.out.println()方法输出一个对象，
 * 或者把某个对象和字符串进行连接运算时，系统会自动调用该对象的toString()方法返回该对象的字符串表示。
 * Object类的toString()方法返回“运行时类名@十六进制hashCode值”格式的字符串，
 * 但很多类都重写了Object类的toString()方法，用于返回可以表述该对象信息的字符串。
 * <p>
 * 除此之外，Object类还提供了wait()、notify()、notifyAll()几个方法，通过这几个方法可以控制线程的暂停和运行。
 * Java还提供了一个protected修饰的clone()方法，该方法用于帮助其他对象来实现“自我克隆”，
 * 所谓“自我克隆”就是得到一个当前对象的副本，而且二者之间完全隔离。
 * 由于Object类提供的clone()方法使用了protected修饰，因此该方法只能被子类重写或调用。
 * 自定义类实现“克隆”的步骤如下。
 * ①自定义类实现Cloneable接口。这是一个标记性的接口，实现该接口的对象可以实现“自我克隆”，接口里没有定义任何方法。
 * ②自定义类实现自己的clone()方法。
 * ③实现clone()方法时通过super.clone()；调用Object实现的clone()方法来得到该对象的副本，并返回该副本。如下程序示范了如何实现“自我克隆”。
 */
class Address {
    String detail;

    public Address(String detail) {
        this.detail = detail;
    }
}

// 实现Cloneable接口
class User implements Cloneable {
    int age;
    Address address;

    public User(int age) {
        this.age = age;
        address = new Address("广州天河");
    }

    // 通过调用super.clone()来实现clone()方法
    public User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
}

class CloneTest {
    static void test() throws CloneNotSupportedException {
        User u1 = new User(29);
        User u2 = u1.clone();
        System.out.println(u1 == u2);   // false
        System.out.println(u1.address == u2.address);   // true
        /*
         * 意思是clone()方法是浅拷贝
         * Object类提供的Clone机制只对对象里各实例变量进行"简单复制"
         * 如果开发者需要对对象进行“深克隆”，则需要开发者自己进行“递归”克隆，保证所有引用类型的成员变量值所引用的对象都被复制了。
         */
    }
}

/**
 * 操作对象的Objects工具类
 * Java 7新增了一个Objects工具类，它提供了一些工具方法来操作对象，这些工具方法大多是“空指针”安全的。
 * 比如你不能确定一个引用变量是否为null，如果贸然地调用该变量的toString()方法，则可能引发NullPointerExcetpion异常；
 * 但如果使用Objects类提供的toString（Object o）方法，就不会引发空指针异常，当o为null时，程序将返回一个"null"字符串。
 * 提示：Java为工具类的命名习惯是添加一个字母s，比如操作数组的工具类是Arrays，操作集合的工具类是Collections。
 */
class ObjectsTest {
    static ObjectsTest obj;

    static void test() {
        System.out.println(Objects.hashCode(obj));
        System.out.println(Objects.toString(obj));
        // 要求obj不能为null，否则引发异常,该方法主要用来对方法形参进行输入校验
//        System.out.println(Objects.requireNonNull(obj, "obj参数不能是null！"));
    }
}

/**
 * Java 9改进的String、StringBuffer和StringBuilder类
 * 字符串就是一连串的字符序列，Java提供了String、StringBuffer和StringBuilder三个类来封装字符串，并提供了一系列方法来操作字符串对象。
 * <p>
 * String类是不可变类，即一旦一个String对象被创建以后，包含在这个对象中的字符序列是不可改变的，直至这个对象被销毁。
 * <p>
 * StringBuffer对象则代表一个字符序列可变的字符串，当一个StringBuffer被创建以后，
 * 通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。
 * 一旦通过StringBuffer生成了最终想要的字符串，就可以调用它的toString()方法将其转换为一个String对象。
 * <p>
 * StringBuilder和StringBuffer基本相似，两个类的构造器和方法也基本相同。
 * 不同的是，StringBuffer是线程安全的，而StringBuilder则没有实现线程安全功能，所以性能略高。
 * 因此在通常情况下，如果需要创建一个内容可变的字符串对象，则应该优先考虑使用StringBuilder类。
 * <p>
 * Java 9改进了字符串（包括String、StringBuffer、StringBuilder）的实现。
 * 在Java 9以前字符串采用char[]数组来保存字符，因此字符串的每个字符占2字节；
 * 而Java 9及更新版本的JDK的字符串采用byte[]数组再加一个encoding-flag字段来保存字符，因此字符串的每个字符只占1字节。
 * 所以Java 9及更新版本的JDK的字符串更加节省空间，但字符串的功能方法没有受到任何影响。
 * 详细API接口可以在API文档中找到
 * <p>
 * 因为String是不可变的，所以会额外产生很多临时变量，使用StringBuffer或StringBuilder就可以避免这个问题。
 * StringBuilder提供了一系列插入、追加、改变该字符串里包含的字符序列的方法。而StringBuffer与其用法完全相同，只是StringBuffer是线程安全的。
 * StringBuilder、StringBuffer有两个属性：length和capacity，其中length属性表示其包含的字符序列的长度。
 * 与String对象的length不同的是，StringBuilder、StringBuffer的length是可以改变的，
 * 可以通过length()、setLength(int len)方法来访问和修改其字符序列的长度。
 * capacity属性表示StringBuilder的容量，capacity通常比length大，程序通常无须关心capacity属性。
 * <p>
 * 如下程序示范了StringBuilder类的用法。
 */
class StringBuilderTest {
    static void test() {
        StringBuilder sb = new StringBuilder();
        sb.append("java");  // sb = "java"
        sb.insert(0, "hello ");  // sb = "hello java"
        sb.replace(5, 6, ",");  // sb = "hello,java"
        sb.delete(5, 6);    // sb = "hellojava"
        sb.reverse();   // 反转   sb = "avajolleh"
        System.out.println(sb);
        System.out.println(sb.length());    // 9
        System.out.println(sb.capacity());  // 16
        // 改变StringBuilder的长度，将只保留前面部分
        sb.setLength(5);
        System.out.println(sb); // avajo
    }
}

/**
 * Math类
 * Java提供了基本的+、－、*、/、%等基本算术运算的运算符，但对于更复杂的数学运算，例如，三角函数、对数运算、指数运算等则无能为力。
 * Java提供了Math工具类来完成这些复杂的运算，Math类是一个工具类，它的构造器被定义成private的，因此无法创建Math类的对象；
 * Math类中的所有方法都是类方法，可以直接通过类名来调用它们。
 * Math类除提供了大量静态方法之外，还提供了两个类变量：PI和E，正如它们名字所暗示的，它们的值分别等于π和e。
 * <p>
 * Math类的所有方法名都明确标识了该方法的作用，读者可自行查阅API来了解Math类各方法的说明。
 * 下面程序示范了Math类的用法。
 */
class MathTest {
    static void test() {
        // 将弧度转换为角度
        System.out.println("Math.toDegrees(1.57): " + Math.toDegrees(1.57));
        // 计算正弦
        System.out.println("Math.sin(1.57): " + Math.sin(1.57));
        // 取整，返回小于目标数的最大整数
        System.out.println("Math.floor(1.2): " + Math.floor(1.2));
        // 取整，返回大于目标数的最小整数
        System.out.println("Math.ceil(1.2): " + Math.ceil(1.2));
        // 四舍五入取整
        System.out.println("Math.round(2.3): " + Math.round(2.3));
        // ...
    }
}

/**
 * ThreadLocalRandom与Random
 * <p>
 * Random类专门用于生成一个伪随机数，它有两个构造器：
 * 一个构造器使用默认的种子（以当前时间作为种子），
 * 另一个构造器需要程序员显式传入一个long型整数的种子。
 * <p>
 * ThreadLocalRandom类是Java 7新增的一个类，它是Random的增强版。
 * 在并发访问的环境下，使用ThreadLocalRandom来代替Random可以减少多线程资源竞争，最终保证系统具有更好的线程安全性。
 * <p>
 * ThreadLocalRandom类的用法与Random类的用法基本相似，它提供了一个静态的current()方法来获取ThreadLocalRandom对象，
 * 获取该对象之后即可调用各种nextXxx()方法来获取伪随机数了。
 * <p>
 * ThreadLocalRandom与Random都比Math的random()方法提供了更多的方式来生成各种伪随机数，可以生成浮点类型的伪随机数，
 * 也可以生成整数类型的伪随机数，还可以指定生成随机数的范围。
 * 关于Random类的用法如下程序所示。
 */
class RandomTest {
    static void test() {
        Random rand = new Random();
        System.out.println("rand.nextBoolean(): " + rand.nextBoolean());
        byte[] buffer = new byte[16];
        rand.nextBytes(buffer);
        System.out.println(Arrays.toString(buffer));
        // 生成0.0-1.0之间的伪随机double数
        System.out.println("rand.nextDouble(): " + rand.nextDouble());
        // 生成0.0-1.0之间的伪随机float数
        System.out.println("rand.nextFloat(): " + rand.nextFloat());
        // 生成平均值是0.0，标准差是1.0的伪高斯数
        System.out.println("rand.nextGaussian(): " + rand.nextGaussian());
        // 生成一个处于int整数取值范围内的伪随机整数
        System.out.println("rand.nextInt(): " + rand.nextInt());
        // 生成0-26之间的伪随机整数
        System.out.println("rand.nextInt(26): " + rand.nextInt(26));
    }
}

/**
 * Random使用一个48位的种子，如果这个类的两个实例是用同一个种子创建的，对它们以同样的顺序调用方法，则它们会产生相同的数字序列。
 * 也就是说，Random产生的数字并不是真正随机的，而是一种伪随机。
 * 为了避免两个Random对象产生相同的数字序列，通常推荐使用当前时间作为Random对象的种子，如下代码所示。
 * Random rand = new Random(System.currentTimeMillis());
 * 在多线程环境下使用ThreadLocalRandom的方式与使用Random基本类似，如下程序片段示范了ThreadLocalRandom的用法。
 */

/**
 * BigDecimal类
 * 前面在介绍float、double两种基本浮点类型时已经指出，这两个基本类型的浮点数容易引起精度丢失。
 * 先看如下程序。
 */
class DoubleTest {
    static void test() {
        System.out.println("0.05 + 0.01 = " + (0.05 + 0.01));
        System.out.println("1.0 - 0.42 = " + (1.0 - 0.42));
        System.out.println("4.015 * 100 = " + (4.015 * 100));
        System.out.println("123.3 / 100 = " + (123.3 / 100));
    }
}

/**
 * 为了能精确表示、计算浮点数，Java提供了BigDecimal类，该类提供了大量的构造器用于创建BigDecimal对象，
 * 包括把所有的基本数值型变量转换成一个BigDecimal对象，也包括利用数字字符串、数字字符数组来创建BigDecimal对象。
 *
 * 当程序使用new BigDecimal(0.1)来创建一个BigDecimal对象时，它的值并不是0.1，它实际上等于一个近似0.1的数。
 * 这是因为0.1无法准确地表示为double浮点数，所以传入BigDecimal构造器的值不会正好等于0.1（虽然表面上等于该值）。
 */
class BigDecimalTest {
    static void test() {
        BigDecimal f1 = new BigDecimal("0.05");
        BigDecimal f2 = BigDecimal.valueOf(0.01);
        BigDecimal f3 = new BigDecimal(0.05);
        System.out.println("BigDecimal(\"0.05\") = " + f1);
        System.out.println("BigDecimal.valueOf(0.01) = " + f2);
        System.out.println("BigDecimal(0.05) = " + f3);
        System.out.println("使用String作为BigDecimal构造器参数：");
        System.out.println("0.05 + 0.01 = " + f1.add(f2));
        System.out.println("0.05 - 0.01 = " + f1.subtract(f2));
        System.out.println("0.05 * 0.01 = " + f1.multiply(f2));
        System.out.println("0.05 / 0.01 = " + f1.divide(f2));
        System.out.println("使用double作为BigDecimal构造器参数：");
        System.out.println("0.05 + 0.01 = " + f3.add(f2));
        System.out.println("0.05 - 0.01 = " + f3.subtract(f2));
        System.out.println("0.05 * 0.01 = " + f3.multiply(f2));
        System.out.println("0.05 / 0.01 = " + f3.divide(f2));
    }
}

/**
 * 注意：创建BigDecimal对象时，不要直接使用double浮点数作为构造器参数来调用BigDecimal构造器，否则同样会发生精度丢失的问题。
 * 如果程序中要求对double浮点数进行加、减、乘、除基本运算，则需要先将double类型数值包装成BigDecimal对象，
 * 调用BigDecimal对象的方法执行运算后再将结果转换成double型变量。
 * 这是比较烦琐的过程，可以考虑以BigDecimal为基础定义一个Arith工具类，该工具类代码如下。
 */
class Arith {
    private static final int DEF_DIV_SCALE = 10;

    // 让这个类不能实例化
    private Arith() {
    }

    // 提供精确的加法运算
    public static double add(double v1, double v2) {
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.add(b2).doubleValue();
    }

    // 提供精确的减法运算
    public static double sub(double v1, double v2) {
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.subtract(b2).doubleValue();
    }

    // 提供精确的乘法运算
    public static double mul(double v1, double v2) {
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.multiply(b2).doubleValue();
    }

    // 提供（相对）精确的除法运算，当发生除不尽的情况时，
    // 精确到小数点以后10位的数字四舍五入
    public static double div(double v1, double v2) {
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.divide(b2, DEF_DIV_SCALE, RoundingMode.HALF_UP).doubleValue();
    }
}

class ArithTest {
    static void test() {
        System.out.println("0.05 + 0.01 = " + Arith.add(0.05, 0.01));
        System.out.println("1.0 - 0.42 = " + Arith.sub(1.0, 0.42));
        System.out.println("4.015 * 100 = " + Arith.mul(4.015, 100));
        System.out.println("123.3 / 100 = " + Arith.div(123.3, 100));
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        CloneTest.test();
        ObjectsTest.test();
        StringBuilderTest.test();
        MathTest.test();
        RandomTest.test();
        DoubleTest.test();
        BigDecimalTest.test();
        ArithTest.test();
    }
}
